package cn.ddiancan.auth.service.provider;

import cn.ddiancan.auth.constant.RequestTypeEnum;
import cn.ddiancan.auth.service.token.XddOAuth2PasswordAuthenticationToken;
import cn.ddiancan.auth.service.userdetails.XddcloudUserDetailsImpl;
import cn.ddiancan.xddcloud.common.exception.ServiceException;
import org.springframework.http.HttpStatus;
import org.springframework.lang.Nullable;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.util.StringUtils;

public class XddOAuth2PasswordAuthenticationProvider implements AuthenticationProvider {

    private final OAuth2TokenGenerator<?> tokenGenerator;

    private final JwtEncoder jwtEncoder;

    private final XddcloudUserDetailsImpl userDetailService;

    private final PasswordEncoder passwordEncoder;

    private final OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer;

    private final AuthorizationServerSettings authorizationServerSettings;

    private final OAuth2AuthorizationService oAuth2AuthorizationService;

    public XddOAuth2PasswordAuthenticationProvider(OAuth2TokenGenerator<?> tokenGenerator, JwtEncoder jwtEncoder,
        XddcloudUserDetailsImpl userDetailService, PasswordEncoder passwordEncoder,
        OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer,
        AuthorizationServerSettings authorizationServerSettings,
        OAuth2AuthorizationService oAuth2AuthorizationService) {
        this.tokenGenerator = tokenGenerator;
        this.jwtEncoder = jwtEncoder;
        this.userDetailService = userDetailService;
        this.passwordEncoder = passwordEncoder;
        this.jwtCustomizer = jwtCustomizer;
        this.authorizationServerSettings = authorizationServerSettings;
        this.oAuth2AuthorizationService = oAuth2AuthorizationService;
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        XddOAuth2PasswordAuthenticationToken passwordAuthentication =
            (XddOAuth2PasswordAuthenticationToken) authentication;
        // 校验账户
        String username = passwordAuthentication.getUsername();
        // 校验密码
        String password = passwordAuthentication.getPassword();
        isTrue(StringUtils.hasText(username) && StringUtils.hasText(password), "username or password cannot be empty");
        // 查询账户信息
        userDetailService.setRequestSource(passwordAuthentication.getRequestSource());
        userDetailService.setLoginType(RequestTypeEnum.valueOf(passwordAuthentication.getRequestType()));
        UserDetails userDetails = userDetailService.loadUserByUsername(username);
        notNull(userDetails, "user info not exists");
        // 校验密码
        isTrue(passwordEncoder.matches(password, userDetails.getPassword()), "账号密码错误，请重新输入！");
        // 构建认证信息
        return OAuth2AccessTokenAuthenticationTokenHelper.auth2AccessTokenAuthenticationToken(passwordAuthentication,
            userDetails, authorizationServerSettings, jwtCustomizer, oAuth2AuthorizationService, tokenGenerator,
            jwtEncoder);
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return XddOAuth2PasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }


    private static void isTrue(boolean expression, String message) {
        if (!expression) {
            throw new ServiceException(HttpStatus.FORBIDDEN.value(), message);
        }
    }

    private static void notNull(@Nullable Object object, String message) {
        if (object == null) {
            throw new ServiceException(HttpStatus.FORBIDDEN.value(), message);
        }
    }
}
